-- Radical by Basil Voronkov
-- Module 'engine'
---------------------------------------------
-- Core game engine
require "theme"
require "click"
require "timer"
require "lib/const"
require "lib/core"
require "lib/table"
require "lib/drawing"

engine = {
  
}

void = 235325636002;

function engine.picbounds(x,y,w,h)
  theme.set("scr.gfx.x", x);
  theme.set("scr.gfx.y", y);
  theme.set("scr.gfx.w", w);
  theme.set("scr.gfx.h", h);
end

function engine.xacts(on,txt)
  return txt:parse(function(s) 
    return "{"..on.."|"..s.."}";
  end);
end

function engine.extract_dsc(s)
  local dsc = "";
  local cc = 0;
  for i,v in ipairs(objs(s)) do
    if v.dsc_scn ~= nil and (v.cnd == nil or rcall(v.cnd, v)) then
      local d = rcall(v.dsc_scn, v);
      if d ~= nil and d ~= "" then
        if cc > 0 then
          dsc = dsc.." ";
        end
        cc = cc + 1;
        dsc = dsc..d;
      end
    end
  end
  if dsc ~= "" then
    return dsc;
  end
end

function engine.previous_pic(s)
  local ret = nil;
  local so = s;
  while true do
    if so.__from__ == nil then
      return const.path.gfx.."black.jpg";
    end
    ret = ref(so.__from__).img;
    so = ref(so.__from__);
    if not so.gen_ then
      return ret;
    end
  end
end

function engine.main(v)
  return room {
     _seen      = false
    ,nam        = "main"
    ,start      = v.start
    ,finish     = v.finish
    ,enter      = function(s)
                    if not s._seen then
                      s._seen = true;
                      rcall(s.start, s);
                    else
                      rcall(s.finish, s);
                      return false;
                    end
                  end
  }
end

function engine.state(o)
  o.nam = "state";
  return obj(o);
end

function engine.hub(o)
  local enter = o.enter;
  return room {
     nam        = "hub"
    ,enter      = function(s)
                    enter(s);
                    s._seen = true;
                  end
  }
end

function engine.gameroom(o)
  local enter = o.enter;
  local exit = o.exit;
  local ret = room {
     nam        = function(s)
                    if s.redraw ~= nil then
                      s:redraw();
                    end
                    return "";
                  end
    ,kbd        = function(s)
                    mainmenu.open();
                    return true;
                  end
    ,pic        = drawing.sprite_blank(const.picwidth, const.picheight, true)
    ,obj        = o.obj
    ,noselitem  = o.noselitem
    ,dscgen     = function(s)
                    local xacts = engine.extract_dsc(s);
                    local ret = o.dsc;
                    if ret ~= nil and (s.showdsc == nil or s:showdsc()) then
                      ret = rcall(ret, s);
                      if xacts ~= nil and ret ~= nil and ret ~= "" then
                        ret = ret.." "..xacts;
                      elseif xacts ~= nil then
                        ret = xacts;
                      end
                    else
                      ret = xacts;
                    end
                    
                    return ret;
                  end
    ,drawbg     = function(s)
                    if s.invalidate ~= nil then
                      s:invalidate();
                    end
                    
                    local pic = sprite.load(const.path.gfx.."black.jpg");
                    if s.tiles ~= nil then
                      for i,v in ipairs(rcall(s.tiles,s)) do
                        pic = s:drawtile(pic, rcall(v,s));
                      end
                    elseif s.tile ~= nil then
                      pic = s:drawtile(pic, rcall(s.tile,s));
                    end
                    
                    local txt = s:dscgen();
                    if txt ~= nil then
                      local bounds = nvl(rcall(s.bounds,s),{});
                      local x,y,w = nvl(bounds[1],20),nvl(bounds[2],40),nvl(bounds[3],800);
                      s.map = nil;
                      if s.tmpdsc ~= nil and s.tmpdsc ~= "" then
                        local fy = drawing.render_multiline_text(s, spr, s.tmpdsc, const.fonti, 14, "black", 0, y, w, false, 0, true);
                        local hh = fy - y - 2;
                        drawing.render_multiline_text(s, pic, s.tmpdsc, const.fonti, 14, const.textcolor, x, y, w);
                        y = y + hh + 10;
                        drawing.draw_mask(pic, x, y, w, 1, const.textcolor, 255)
                        y = y + 10;
                      end
                      drawing.render_multiline_text(s, pic, txt, const.font, 14, const.textcolor, x, y, w, false, 0, false, true);
                    end
                    
                    if s.ondrawbg ~= nil then
                      pic = s:ondrawbg(pic);
                    end
                    
                    local b = sprite.box(const.picwidth, 20, "black");
                    sprite.copy(b, pic, 0, 0);
                    sprite.free(b);
                    
                    local b = sprite.box(const.picwidth, 1, const.textcolor);
                    sprite.copy(b, pic, 0, 19);
                    sprite.free(b);
                    
                    theme.gfx.bg(pic);
                    s:draw();
                  end
    ,drawtile   = function(s,p,t)
                    if t == nil or t == void then
                      return p;
                    end
                    local tp = t;
                    if not is_string(t) then
                      tp = t[1];
                    end
                    local spr = sprite.load(tp);
                    if spr == nil then
                      raise("Unable to load sprite: "..tp);
                    end
                    if not is_string(t) and t[2] ~= nil and t[3] ~= nil then
                      sprite.copy(spr, p, t[2], t[3]);
                    else
                      local w,h = sprite.size(spr);
                      sprite.copy(spr, p, 0, const.picheight-h);
                    end
                    sprite.free(spr);
                    return p;
                  end
    ,draw       = function(s)
                    s:clean();
                    if s.ondraw ~= nil then
                      s:ondraw(s.pic);
                    end
                  end
    ,clean      = function(s)
                    if not o.noclean then
                      drawing.clean(s.pic, 0, 0, const.picwidth, const.picheight);
                    end
                  end
    ,redraw     = function(s)
                    s:drawbg();
                    s:draw();
                    if s.onredraw ~= nil then
                      s:onredraw();
                    end
                  end
    ,enter      = function(s,f)
                    if s.fps ~= nil and s.fps > 0 then
                      timer:set(1000/s.fps);
                    end
                    if enter ~= nil then
                      enter(s,f);
                    end
                  end
    ,entered    = o.entered
    ,left       = o.left
    ,exit       = function(s)
                    s._seen = true;
                    if s.fps ~= nil and s.fps > 0 then
                      timer:stop();
                    end
                    if s.tilecache ~= nil then
                      sprite.free(s.tilecache);
                      s.tilecache = nil;
                    end
                    if exit ~= nil then
                      exit(s);
                    end
                  end
    ,timer      = function(s)
                    local x,y = mouse_pos();
                    if s.ontimer ~= nil then
                      s:ontimer(x, y);
                    end
                    if s.map ~= nil then
                      local sel = s.map:get(x, y);
                      if s.oldlink ~= nil and sel ~= s.oldlink.sel then
                        drawing.clean(s.pic, s.oldlink.x-1, s.oldlink.y, s.oldlink.w+3, 1);
                        drawing.clean(s.pic, s.oldlink.x-1, s.oldlink.y+s.oldlink.h, s.oldlink.w+3, 1);
                        drawing.clean(s.pic, s.oldlink.x-1, s.oldlink.y, 1, s.oldlink.h);
                        drawing.clean(s.pic, s.oldlink.x+s.oldlink.w+1, s.oldlink.y, 1, s.oldlink.h);
                        
                        s.oldlink = nil;
                      end
                      if sel ~= nil then
                        local it = s.map.items_[sel];
                        local tx,ty,tw,th = it[1],it[2]-20,it[3],it[4];
                        local b1 = sprite.box(tw+3, 1, const.textcolor);
                        local b2 = sprite.box(1, th, const.textcolor);
                        sprite.copy(b1, s.pic, tx-1, ty);
                        sprite.copy(b1, s.pic, tx-1, ty+th);
                        sprite.copy(b2, s.pic, tx-1, ty);
                        sprite.copy(b2, s.pic, tx+tw+1, ty);
                        sprite.free(b1);
                        sprite.free(b2);
                        s.oldlink = { sel = sel, x = tx, y = ty, w = tw, h = th };
                      end
                    end
                  end
    ,click      = function(s, button)
                    local x,y = mouse_pos();
                    if s.onclick ~= nil and button == 1 then
                      if s:onclick(x, y) then
                        return;
                      end
                    end
                    if s.onclickr ~= nil and button == 3 then
                      if s:onclickr(x, y) then
                        return;
                      end
                    end
                    if s.map ~= nil then
                      local sel = s.map:get(x, y);
                      if sel == "localact" then
                        s:localact();
                      elseif sel ~= nil and is_string(sel) and sel:startsWith("walk(") then
                        exec(sel);
                      elseif sel ~= nil then
                        local so = ref(sel);
                        if so ~= nil then
                          if so.act == nil then
                            error("Act is nil in object: "..sel);
                          end
                          so:act();
                        end
                      end
                    end
                  end
  }
  copy_attr(o, ret);
  ret.dsc = nil;
  return ret;
end

function engine.item(o)
  local ret = obj {
     nam        = nvl(o.nam, "x01")
    ,obj        = o.obj
    ,append_    = o.append
    ,dsc_scn    = function(s)
                    local txt = rcall(o.dsc, s);
                    if txt ~= nil and (s.cnd == nil or rcall(s.cnd,s)) then
                      return txt:parse(function(st) 
                        local arr = st:split("|");
                        local c = 0;
                        for i,v in arr do
                          c = c + 1;
                        end
                        if c > 1 then
                          return "{"..st.."}";
                        else
                          return "{"..deref(s).."|"..st.."}";
                        end
                      end);
                    end
                    return txt;
                  end
    ,act        = function(s)
                    local r = rcall(o.act,s);
                    if is_string(r) then
                      if s.append_ and here().tmpdsc ~= nil then
                        here().tmpdsc = here().tmpdsc .. r;
                      else
                        here().tmpdsc = r;
                      end
                      refresh();
                    else
                      return r;
                    end
                  end
  }
  copy_attr(o, ret);
  ret.dsc = nil;
  return ret;
end